from Factors.General import *
from sklearn import linear_model

def VolatilityCallBack(dfData, factorName, params={}):
    # mad = lambda x: np.fabs(x - x.mean()).mean()
    # volatility = np.sum(np.square(x - x.mean())) / count
    # apple['Close'].rolling(window=5).apply(mad)
    # dfData[factorName] = dfData['DailyReturn'].rolling(window=ANNULIZED_FACTOR).std()

    #
    dfData[factorName] = np.nan

    # Return
    dfData["DailyReturn"] = dfData["BClose"] / dfData["BClose"].shift(1) - 1

    #
    period = params["Period"]
    sqrtPeriod = math.sqrt(period)
    #
    for i in range(period - 1, len(dfData)):
        # ---准备数据---
        slice = dfData[i - (period - 1):i + 1]
        volatility = slice["DailyReturn"].std() * sqrtPeriod
        dfData.at[i, factorName] = volatility
        # dfData.loc[i, factorName + "1"] = volatility
        pass

    # print(dfData.tail())
    pass


# https://breakingdownfinance.com/finance-topics/risk-management/parkinson-volatility/
def HighLowVolatilityCallBack(dfData, factorName, params={}):

    #print(dfData.head())

    #
    dfData[factorName] = np.nan

    # Return
    dfData["DailyReturn"] = np.log(dfData["BHigh"] / dfData["BLow"])
    dfData["DailyReturn"] = dfData["DailyReturn"] * dfData["DailyReturn"]

    #
    period = params["Period"]
    # sqrtPeriod = math.sqrt(period)
    factor = 1 / (4 * np.log(2))
    #
    for i in range(period - 1, len(dfData)):
        # ---准备数据---
        slice = dfData[i - (period - 1):i + 1]
        volatility = slice["DailyReturn"].sum() * factor
        volatility = np.sqrt(volatility)
        dfData.at[i, factorName] = volatility
        # dfData.loc[i, factorName + "1"] = volatility
        pass

    # print(dfData.tail())
    pass


def CalcVolatilityFactor(database, instruments, datetime1, datetime2, update=False):
    print("Calc Volatility")
    CalcDailyFactor(database, instruments, datetime1, datetime2, factorName="Volatility_Annually",
                    callback=VolatilityCallBack, update=update, params={"Period": ANNULIZED_FACTOR})

    CalcDailyFactor(database, instruments, datetime1, datetime2, factorName="Volatility_HighLow",
                    callback=HighLowVolatilityCallBack, update=update, params={"Period": ANNULIZED_FACTOR}, startIndex=9)


# --- Market MarketBeta / MarketAlpha / IdioValotility (HS300)---
def CalcCAPModel(database, instruments, datetime1, datetime2, update=False):

    period = ANNULIZED_FACTOR
    sqrtPeriod = math.sqrt(period)

    count = 0
    for instrument in instruments:
        symbol = instrument["Symbol"]
        count = count + 1
        #if count < 2512:
        #    continue

        #if symbol == "000755.SZ":
        #    continue
        #continue
        #
        print("Calculating CAPM model for " + symbol  + " count:" + str(count)  + " @Time " + str(datetime.datetime.now()))

        # ---确定起始日期---
        realDateTime1, realDateTime2 = DeterminFactorCalculatingRange(database, instrument, datetime1, datetime2,
                                                                      "CAPMBeta_Annually", update)

        if realDateTime1 == realDateTime2:
            print("Unnecessary Update / Delisted or Updated " + symbol)
            continue

        # ---依赖至少一年数据---
        referenceDateTime1 = realDateTime1 + datetime.timedelta(days=-400)

        # ---Target Stock---
        dfStock = IO.LoadFactorsAsDataFrame(database, symbol, datetime1=referenceDateTime1, datetime2=realDateTime2, factors=["DailyLogReturn"])
        length = len(dfStock)
        # print(dfStock.head())
        if len(dfStock) == 0:
            print("No Log Return Factor for" + symbol)
            continue

        # ---新股，数据不足的情况---
        if len(dfStock) < period:
            print("Not Enough Data to CalcBeta " + symbol)
            continue

        rangeBegin = dfStock.loc[0, "DateTime"]
        rangeEnd = dfStock.loc[len(dfStock) - 1, "DateTime"]
        #print(dfStock.head())

        #---Checking Missing Value---
        #dfMissing = dfStock[dfStock.isnull().values==True]
        #if dfMissing.empty != True:
        #    print(dfMissing)

        # ---Market Data---
        dfMarket = IO.LoadFactorsAsDataFrame(database, "000300.SH", datetime1=rangeBegin, datetime2=rangeEnd, factors=["DailyLogReturn"])
        if len(dfMarket) == 0:
            print("Market Data Not Existed (Symbol Traded Too Early) " + symbol)
            continue

        #print(dfMarket.head())

        #---对齐Market数据---
        dfTrain = pd.merge(dfMarket, dfStock, on='DateTime',how='left')
        # dfTrain["DateTime"] = pd.to_datetime(dfTrain["DateTime"])
        #print(dfTrain.head())

        dfTrain.fillna(0, inplace=True)

        dfTrain.rename(columns={'DailyLogReturn_x': "MarketReturns", "DailyLogReturn_y": "StockReturns"}, inplace=True)
        # dfTrain.to_csv("d:/Data/CAPM/" + symbol + "_" + Gadget.ToDateString(rangeBegin) + "_" + Gadget.ToDateString(rangeEnd) + ".csv")
        #print(dfTrain.head())
        #length = len(dfTrain)
        #dfMissing = dfStock[dfTrain.isnull().values==True]
        #if dfMissing.empty != True:
        #    print(dfMissing)

        print(dfTrain.iloc[0]["DateTime"], dfTrain.iloc[-1]["DateTime"])

        # ---创建容器---
        betaDataSeries = DataSeries.DataSeries(symbol + "_CAPMBeta_Annually_Factor")
        alphaDataSerie = DataSeries.DataSeries(symbol + "_CAPMAlpha_Annually_Factor")
        idioDataSeries = DataSeries.DataSeries(symbol + "_IdioValotility_CAPM_Factor")

        #for index, row in dfTrain.iterrows():
        #   pass

        for i in range(period-1, len(dfTrain)):
            # ---准备数据---
            # stdDateTime = dfTrain.at[i, "StdDateTime"]
            # curDateTime = Gadget.ToLocalDateTime(stdDateTime)
            # curDateTime = Gadget.ToDate(curDateTime)
            curDateTime = dfTrain.at[i, "DateTime"]
            curDateTime = pd.Timestamp.to_pydatetime(curDateTime)
            # stdDateTime = stdDateTime.tz_localize("Asia/Shanghai")

            # ---僅僅從本次需求開始時間計算---
            if curDateTime < realDateTime1:
                continue

            slice = dfTrain[i-(period-1):i+1]
            yStockRets = slice[["StockReturns"]]
            xMarketRets = slice[["MarketReturns"]]

            #---不进行ToList，也可以放入Scikit-Learn运算（即Scikit-Learn，也接受DataFrame类型变量）---
            xMarketRets = xMarketRets.values.tolist()
            yStockRets = yStockRets.values.tolist()

            #---回归模型---
            linreg = linear_model.LinearRegression(copy_X=True, fit_intercept=True, n_jobs=1, normalize=False)
            model = linreg.fit(xMarketRets, yStockRets)
            marketAlpha = model.intercept_[0] * period
            marketBeta = model.coef_[0][0]
            residuals = (linreg.predict(xMarketRets) - yStockRets)
            idioValotility = np.std(residuals) * sqrtPeriod

            #
            # print(str(stdDateTime) + " " + str(marketAlpha) + " " + str(marketBeta) + " " + str(idioValotility))

            # ---Write to Database---
            betaDataSeries.Add(GenarateDocument(symbol, curDateTime, marketBeta, createKey=True))
            alphaDataSerie.Add(GenarateDocument(symbol, curDateTime, marketAlpha, createKey=True))
            idioDataSeries.Add(GenarateDocument(symbol, curDateTime, idioValotility, createKey=True))
            pass

        #print(dfTrain.tail(10))
        database.SaveDataSeries(betaDataSeries)
        database.SaveDataSeries(alphaDataSerie)
        database.SaveDataSeries(idioDataSeries)
        pass
